Micron Document
πŸŽ–οΈGitΠ―Ρ€Π°πŸŽ–οΈ


Displaying Raw β€’ Download

core/network/src/commonMain/kotlin/org/meshtastic/core/network/radio/MockRadioTransport.kt bd2863243bab6eb213401d949839a2bc74dde7e2 (bd286324) Text, 35.49 KB

T8b949e/*
* Copyright (c) 2026 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
Tff7b72package T7ee787org.meshtastic.core.network.radio

Tff7b72import T7ee787co.touchlab.kermit.Logger
Tff7b72import T7ee787kotlinx.atomicfu.atomic
Tff7b72import T7ee787kotlinx.coroutines.CoroutineScope
Tff7b72import T7ee787kotlinx.coroutines.Job
Tff7b72import T7ee787kotlinx.coroutines.SupervisorJob
Tff7b72import T7ee787kotlinx.coroutines.cancelAndJoin
Tff7b72import T7ee787kotlinx.coroutines.delay
Tff7b72import T7ee787okio.ByteString.Companion.encodeUtf8
Tff7b72import T7ee787okio.ByteString.Companion.toByteString
Tff7b72import T7ee787org.meshtastic.core.common.util.handledLaunch
Tff7b72import T7ee787org.meshtastic.core.common.util.nowSeconds
Tff7b72import T7ee787org.meshtastic.core.model.Channel
Tff7b72import T7ee787org.meshtastic.core.model.NodeAddress
Tff7b72import T7ee787org.meshtastic.core.repository.HandshakeConstants
Tff7b72import T7ee787org.meshtastic.core.repository.RadioTransport
Tff7b72import T7ee787org.meshtastic.core.repository.RadioTransportCallback
Tff7b72import T7ee787org.meshtastic.proto.AdminMessage
Tff7b72import T7ee787org.meshtastic.proto.Config
Tff7b72import T7ee787org.meshtastic.proto.Data
Tff7b72import T7ee787org.meshtastic.proto.DeviceMetadata
Tff7b72import T7ee787org.meshtastic.proto.DeviceMetrics
Tff7b72import T7ee787org.meshtastic.proto.EnvironmentMetrics
Tff7b72import T7ee787org.meshtastic.proto.FromRadio
Tff7b72import T7ee787org.meshtastic.proto.HardwareModel
Tff7b72import T7ee787org.meshtastic.proto.MeshPacket
Tff7b72import T7ee787org.meshtastic.proto.ModuleConfig
Tff7b72import T7ee787org.meshtastic.proto.Neighbor
Tff7b72import T7ee787org.meshtastic.proto.NeighborInfo
Tff7b72import T7ee787org.meshtastic.proto.NodeInfo
Tff7b72import T7ee787org.meshtastic.proto.PortNum
Tff7b72import T7ee787org.meshtastic.proto.QueueStatus
Tff7b72import T7ee787org.meshtastic.proto.Routing
Tff7b72import T7ee787org.meshtastic.proto.StatusMessage
Tff7b72import T7ee787org.meshtastic.proto.Telemetry
Tff7b72import T7ee787org.meshtastic.proto.ToRadio
Tff7b72import T7ee787org.meshtastic.proto.User
Tff7b72import T7ee787org.meshtastic.proto.Channel Tff7b72as Te6edf3ProtoChannel
Tff7b72import T7ee787org.meshtastic.proto.MyNodeInfo Tff7b72as Te6edf3ProtoMyNodeInfo
Tff7b72import T7ee787org.meshtastic.proto.Position Tff7b72as Te6edf3ProtoPosition

Tff7b72private Tff7b72val Te6edf3defaultLoRaConfig Tff7b72=
Te6edf3ConfigTb4b4b4.Te6edf3LoRaConfigTb4b4b4(
Te6edf3use_preset Tff7b72= Tff7b72trueTb4b4b4,
Te6edf3modem_preset Tff7b72= Te6edf3ConfigTb4b4b4.Te6edf3LoRaConfigTb4b4b4.Te6edf3ModemPresetTb4b4b4.Te6edf3LONG_FASTTb4b4b4,
Te6edf3region Tff7b72= Te6edf3ConfigTb4b4b4.Te6edf3LoRaConfigTb4b4b4.Te6edf3RegionCodeTb4b4b4.Te6edf3USTb4b4b4,
Te6edf3hop_limit Tff7b72= T79c0ff3Tb4b4b4,
Te6edf3tx_enabled Tff7b72= Tff7b72trueTb4b4b4,
Tb4b4b4)

Tff7b72private Tff7b72val Te6edf3defaultChannel Tff7b72= Te6edf3ProtoChannelTb4b4b4(Te6edf3settings Tff7b72= Te6edf3ChannelTb4b4b4.Te6edf3defaultTb4b4b4.Te6edf3settingsTb4b4b4, Te6edf3role Tff7b72= Te6edf3ProtoChannelTb4b4b4.Te6edf3RoleTb4b4b4.Te6edf3PRIMARYTb4b4b4)

T8b949e/**
* A simulated radio, selected as "Demo Mode" in the Connections screen.
*
* It answers the app's real two-stage `want_config` handshake and then feeds a small synthetic mesh β€” a populated node
* list with positions, signal readings and battery levels, one channel conversation, one direct-message thread, and
* telemetry that keeps accruing while the app is open. That makes the app fully explorable without any LoRa hardware,
* which is what an app-store reviewer and a first-time user both need.
*
* Three properties of the real protocol have to be honoured or the app never leaves "Loading node list":
* 1. The handshake is two-stage ([HandshakeConstants.CONFIG_NONCE] then [HandshakeConstants.NODE_INFO_NONCE]). Each
* stage must answer with only its own frames and its own `config_complete_id`. Re-sending `my_info` on stage 2
* resets the app's handshake state machine and the stage-2 completion is then rejected.
* 2. `node_info` frames are only accepted inside the handshake window, so every simulated node has to ship in stage 2.
* 3. Radio metrics (SNR/RSSI/hops) are only taken from packets that look like direct LoRa receptions β€”
* `transport_mechanism = TRANSPORT_LORA` and `hop_start == hop_limit`. Frames left at the proto defaults are treated
* as internal traffic and contribute no signal information at all.
*/
Tf0883e@SuppressTb4b4b4(Ta5d6ff"Ta5d6ffdetekt:TooManyFunctionsTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ffdetekt:MagicNumberTa5d6ff"Tb4b4b4)
Tff7b72class T56d364MockRadioTransportTb4b4b4(
Tff7b72private Tff7b72val Te6edf3callbackTb4b4b4: Te6edf3RadioTransportCallbackTb4b4b4,
Tff7b72private Tff7b72val Te6edf3scopeTb4b4b4: Te6edf3CoroutineScopeTb4b4b4,
Tff7b72val Te6edf3addressTb4b4b4: Tffa657StringTb4b4b4,
Tb4b4b4) Tb4b4b4: Te6edf3RadioTransport Tb4b4b4{

T8b949e/**
* Hands out packet ids.
*
* Atomic because ids are drawn concurrently from the seed pass, the live-traffic ticker, the delayed reply jobs and
* the ack path. Two frames sharing an id is not cosmetic here: the app keys its message and node lists by packet
* id, and a duplicate key has crashed those lists before β€” exactly the screens a store reviewer is looking at while
* the demo runs.
*/
Tff7b72private Tff7b72val Te6edf3packetIdCounter Tff7b72= Te6edf3atomicTb4b4b4(Te6edf3FIRST_PACKET_IDTb4b4b4)

T8b949e/** Guards against re-seeding traffic if the app repeats stage 2 (e.g. after a handshake retry). */
Tff7b72private Tff7b72val Te6edf3trafficStarted Tff7b72= Te6edf3atomicTb4b4b4(Tff7b72falseTb4b4b4)

Tff7b72private Tff7b72val Te6edf3lifecycle Tff7b72= Te6edf3TransportLifecycleGateTb4b4b4(Ta5d6ff"Ta5d6ffMockTa5d6ff"Tb4b4b4)
Tff7b72private Tff7b72val Te6edf3transportJob Tff7b72= Te6edf3SupervisorJobTb4b4b4(Te6edf3scopeTb4b4b4.Te6edf3coroutineContextTff7b72[Te6edf3JobTff7b72]Tb4b4b4)
Tff7b72private Tff7b72val Te6edf3transportScope Tff7b72= Te6edf3CoroutineScopeTb4b4b4(Te6edf3scopeTb4b4b4.Te6edf3coroutineContext Tff7b72+ Te6edf3transportJobTb4b4b4)

Tff7b72private Tff7b72fun Td2a8ffnextPacketIdTb4b4b4(Tb4b4b4)Tb4b4b4: Tffa657Int Tff7b72= Te6edf3packetIdCounterTb4b4b4.Te6edf3getAndIncrementTb4b4b4(Tb4b4b4)

Tff7b72override Tff7b72fun Td2a8ffstartTb4b4b4(Tb4b4b4) Tb4b4b4{
Te6edf3lifecycleTb4b4b4.Te6edf3runIfOpen Tb4b4b4{
Te6edf3LoggerTb4b4b4.Te6edf3i Tb4b4b4{ Ta5d6ff"Ta5d6ffStarting the mock transportTa5d6ff" Tb4b4b4}
Te6edf3callbackTb4b4b4.Te6edf3onConnectTb4b4b4(Tb4b4b4) T8b949e// Tell clients they can use the API
Tb4b4b4}
Tb4b4b4}

Tff7b72override Tff7b72fun Td2a8ffhandleSendToRadioTb4b4b4(Te6edf3pTb4b4b4: Te6edf3ByteArrayTb4b4b4)Tb4b4b4: Tffa657Boolean Tff7b72= Te6edf3lifecycleTb4b4b4.Te6edf3runIfOpen Tb4b4b4{
Tff7b72val Te6edf3pr Tff7b72= Te6edf3runCatching Tb4b4b4{ Te6edf3ToRadioTb4b4b4.Te6edf3ADAPTERTb4b4b4.Te6edf3decodeTb4b4b4(Te6edf3pTb4b4b4) Tb4b4b4}Tb4b4b4.Te6edf3getOrNullTb4b4b4(Tb4b4b4)
Tff7b72if Tb4b4b4(Te6edf3pr Tff7b72=Tff7b72= Tff7b72nullTb4b4b4) Tb4b4b4{
Te6edf3LoggerTb4b4b4.Te6edf3w Tb4b4b4{ Ta5d6ff"Ta5d6ffIgnoring undecodable ToRadio sent to mock transport (Tffd700${Te6edf3pTb4b4b4.Te6edf3sizeTffd700}Ta5d6ff bytes)Ta5d6ff" Tb4b4b4}
Tff7b72returnTf0883e@runIfOpen Tff7b72false
Tb4b4b4}

T8b949e// Intercept the want_config handshake. Real firmware answers each stage separately and only when asked,
T8b949e// and the app's state machine depends on that: see the class doc.
Tff7b72when Tb4b4b4(Te6edf3prTb4b4b4.Te6edf3want_config_idTb4b4b4) Tb4b4b4{
Tff7b72nullTb4b4b4,
T79c0ff0Tb4b4b4,
Tff7b72-Tff7b72> Te6edf3handleOutboundTrafficTb4b4b4(Te6edf3prTb4b4b4)

Te6edf3HandshakeConstantsTb4b4b4.Te6edf3CONFIG_NONCE Tff7b72-Tff7b72> Te6edf3sendConfigStageTb4b4b4(Tb4b4b4)

Te6edf3HandshakeConstantsTb4b4b4.Te6edf3NODE_INFO_NONCE Tff7b72-Tff7b72> Te6edf3sendNodeInfoStageTb4b4b4(Tb4b4b4)

Tff7b72else Tff7b72-Tff7b72> Te6edf3LoggerTb4b4b4.Te6edf3w Tb4b4b4{ Ta5d6ff"Ta5d6ffMock transport ignoring unknown want_config_id Tffd700${Te6edf3prTb4b4b4.Te6edf3want_config_idTffd700}Ta5d6ff" Tb4b4b4}
Tb4b4b4}
Tff7b72true
Tb4b4b4} Tff7b72?: Tff7b72false

Tff7b72private Tff7b72fun Td2a8ffhandleOutboundTrafficTb4b4b4(Te6edf3prTb4b4b4: Te6edf3ToRadioTb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3packet Tff7b72= Te6edf3prTb4b4b4.Te6edf3packet
Tff7b72if Tb4b4b4(Te6edf3packet Tff7b72!Tff7b72= Tff7b72nullTb4b4b4) Tb4b4b4{
Te6edf3sendQueueStatusTb4b4b4(Te6edf3packetTb4b4b4.Te6edf3idTb4b4b4)
Tb4b4b4}

Tff7b72val Te6edf3data Tff7b72= Te6edf3packetTff7b72?.Te6edf3decoded

Tff7b72when Tb4b4b4{
Te6edf3data Tff7b72!Tff7b72= Tff7b72null Tff7b72&Tff7b72& Te6edf3dataTb4b4b4.Te6edf3portnum Tff7b72=Tff7b72= Te6edf3PortNumTb4b4b4.Te6edf3ADMIN_APP Tff7b72-Tff7b72> Tb4b4b4{
Tff7b72val Te6edf3admin Tff7b72= Te6edf3runCatching Tb4b4b4{ Te6edf3AdminMessageTb4b4b4.Te6edf3ADAPTERTb4b4b4.Te6edf3decodeTb4b4b4(Te6edf3dataTb4b4b4.Te6edf3payloadTb4b4b4) Tb4b4b4}Tb4b4b4.Te6edf3getOrNullTb4b4b4(Tb4b4b4)
Tff7b72if Tb4b4b4(Te6edf3admin Tff7b72=Tff7b72= Tff7b72nullTb4b4b4) Tb4b4b4{
Te6edf3LoggerTb4b4b4.Te6edf3w Tb4b4b4{ Ta5d6ff"Ta5d6ffIgnoring undecodable AdminMessage sent to mock transportTa5d6ff" Tb4b4b4}
Tb4b4b4} Tff7b72else Tb4b4b4{
Te6edf3handleAdminPacketTb4b4b4(Te6edf3prTb4b4b4, Te6edf3adminTb4b4b4)
Tb4b4b4}
Tb4b4b4}

Te6edf3data Tff7b72!Tff7b72= Tff7b72null Tff7b72&Tff7b72& Te6edf3dataTb4b4b4.Te6edf3portnum Tff7b72=Tff7b72= Te6edf3PortNumTb4b4b4.Te6edf3TEXT_MESSAGE_APP Tff7b72-Tff7b72> Tb4b4b4{
Tff7b72if Tb4b4b4(Te6edf3packetTff7b72?.Te6edf3want_ack Tff7b72=Tff7b72= Tff7b72trueTb4b4b4) Te6edf3sendFakeAckTb4b4b4(Te6edf3prTb4b4b4)
Te6edf3sendSimulatedReplyTb4b4b4(Te6edf3packetTb4b4b4)
Tb4b4b4}

Te6edf3packet Tff7b72!Tff7b72= Tff7b72null Tff7b72&Tff7b72& Te6edf3packetTb4b4b4.Te6edf3want_ack Tff7b72=Tff7b72= Tff7b72true Tff7b72-Tff7b72> Te6edf3sendFakeAckTb4b4b4(Te6edf3prTb4b4b4)

Tff7b72else Tff7b72-Tff7b72> Te6edf3LoggerTb4b4b4.Te6edf3i Tb4b4b4{ Ta5d6ff"Ta5d6ffIgnoring data sent to mock transport Tffd700$Te6edf3prTa5d6ff" Tb4b4b4}
Tb4b4b4}
Tb4b4b4}

Tff7b72private Tff7b72fun Td2a8ffhandleAdminPacketTb4b4b4(Te6edf3prTb4b4b4: Te6edf3ToRadioTb4b4b4, Te6edf3dTb4b4b4: Te6edf3AdminMessageTb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3packet Tff7b72= Te6edf3prTb4b4b4.Te6edf3packet Tff7b72?: Tff7b72return
Tff7b72when Tb4b4b4{
Te6edf3dTb4b4b4.Te6edf3get_config_request Tff7b72=Tff7b72= Te6edf3AdminMessageTb4b4b4.Te6edf3ConfigTypeTb4b4b4.Te6edf3LORA_CONFIG Tff7b72-Tff7b72>
Te6edf3sendAdminTb4b4b4(Te6edf3packetTb4b4b4.Te6edf3toTb4b4b4, Te6edf3packetTb4b4b4.Te6edf3fromTb4b4b4, Te6edf3packetTb4b4b4.Te6edf3idTb4b4b4) Tb4b4b4{
Te6edf3copyTb4b4b4(Te6edf3get_config_response Tff7b72= Te6edf3ConfigTb4b4b4(Te6edf3lora Tff7b72= Te6edf3defaultLoRaConfigTb4b4b4)Tb4b4b4)
Tb4b4b4}

Tb4b4b4(Te6edf3dTb4b4b4.Te6edf3get_channel_request Tff7b72?: T79c0ff0Tb4b4b4) Tff7b72!Tff7b72= T79c0ff0 Tff7b72-Tff7b72>
Te6edf3sendAdminTb4b4b4(Te6edf3packetTb4b4b4.Te6edf3toTb4b4b4, Te6edf3packetTb4b4b4.Te6edf3fromTb4b4b4, Te6edf3packetTb4b4b4.Te6edf3idTb4b4b4) Tb4b4b4{
Te6edf3copyTb4b4b4(
Te6edf3get_channel_response Tff7b72=
Te6edf3ProtoChannelTb4b4b4(
Te6edf3index Tff7b72= Tb4b4b4(Te6edf3dTb4b4b4.Te6edf3get_channel_request Tff7b72?: T79c0ff0Tb4b4b4) Tff7b72- T79c0ff1Tb4b4b4, T8b949e// 0 based on the response
Te6edf3settings Tff7b72= Tff7b72if Tb4b4b4(Te6edf3dTb4b4b4.Te6edf3get_channel_request Tff7b72=Tff7b72= T79c0ff1Tb4b4b4) Te6edf3ChannelTb4b4b4.Te6edf3defaultTb4b4b4.Te6edf3settings Tff7b72else Tff7b72nullTb4b4b4,
Te6edf3role Tff7b72=
Tff7b72if Tb4b4b4(Te6edf3dTb4b4b4.Te6edf3get_channel_request Tff7b72=Tff7b72= T79c0ff1Tb4b4b4) Tb4b4b4{
Te6edf3ProtoChannelTb4b4b4.Te6edf3RoleTb4b4b4.Te6edf3PRIMARY
Tb4b4b4} Tff7b72else Tb4b4b4{
Te6edf3ProtoChannelTb4b4b4.Te6edf3RoleTb4b4b4.Te6edf3DISABLED
Tb4b4b4}Tb4b4b4,
Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Tb4b4b4}

Te6edf3dTb4b4b4.Te6edf3get_module_config_request Tff7b72=Tff7b72= Te6edf3AdminMessageTb4b4b4.Te6edf3ModuleConfigTypeTb4b4b4.Te6edf3STATUSMESSAGE_CONFIG Tff7b72-Tff7b72>
Te6edf3sendAdminTb4b4b4(Te6edf3packetTb4b4b4.Te6edf3toTb4b4b4, Te6edf3packetTb4b4b4.Te6edf3fromTb4b4b4, Te6edf3packetTb4b4b4.Te6edf3idTb4b4b4) Tb4b4b4{
Te6edf3copyTb4b4b4(
Te6edf3get_module_config_response Tff7b72=
Te6edf3ModuleConfigTb4b4b4(Te6edf3statusmessage Tff7b72= Te6edf3ModuleConfigTb4b4b4.Te6edf3StatusMessageConfigTb4b4b4(Te6edf3node_status Tff7b72= Te6edf3MY_NODE_STATUSTb4b4b4)Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Tb4b4b4}

Tff7b72else Tff7b72-Tff7b72> Te6edf3LoggerTb4b4b4.Te6edf3i Tb4b4b4{ Ta5d6ff"Ta5d6ffIgnoring admin sent to mock transport Tffd700$Te6edf3dTa5d6ff" Tb4b4b4}
Tb4b4b4}
Tb4b4b4}

Tff7b72override Tff7b72suspend Tff7b72fun Td2a8ffcloseTb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3completed Tff7b72=
Te6edf3lifecycleTb4b4b4.Te6edf3closeTb4b4b4(
Te6edf3beforeDrain Tff7b72= Tb4b4b4{ Te6edf3transportJobTb4b4b4.Te6edf3cancelAndJoinTb4b4b4(Tb4b4b4) Tb4b4b4}Tb4b4b4,
Te6edf3teardown Tff7b72= Tb4b4b4{ Te6edf3LoggerTb4b4b4.Te6edf3i Tb4b4b4{ Ta5d6ff"Ta5d6ffClosing the mock transportTa5d6ff" Tb4b4b4} Tb4b4b4}Tb4b4b4,
Tb4b4b4)
Tff7b72if Tb4b4b4(Tff7b72!Te6edf3completedTb4b4b4) Te6edf3LoggerTb4b4b4.Te6edf3w Tb4b4b4{ Ta5d6ff"Ta5d6ffMock transport teardown did not complete within its lifecycle boundsTa5d6ff" Tb4b4b4}
Tb4b4b4}

T8b949e// ── Handshake ─────────────────────────────────────────────────────────────────────────────

T8b949e/** Stage 1: our own identity, device metadata, config and channels. Deliberately carries no `node_info`. */
Tff7b72private Tff7b72fun Td2a8ffsendConfigStageTb4b4b4(Tb4b4b4) Tb4b4b4{
Te6edf3LoggerTb4b4b4.Te6edf3d Tb4b4b4{ Ta5d6ff"Ta5d6ffMock transport answering config stageTa5d6ff" Tb4b4b4}
Tff7b72val Te6edf3metadata Tff7b72= Te6edf3DeviceMetadataTb4b4b4(Te6edf3firmware_version Tff7b72= Te6edf3FIRMWARE_VERSIONTb4b4b4, Te6edf3hw_model Tff7b72= Te6edf3HardwareModelTb4b4b4.Te6edf3ANDROID_SIMTb4b4b4)
Tff7b72val Te6edf3frames Tff7b72=
Te6edf3listOfTb4b4b4(
Te6edf3FromRadioTb4b4b4(Te6edf3my_info Tff7b72= Te6edf3ProtoMyNodeInfoTb4b4b4(Te6edf3my_node_num Tff7b72= Te6edf3MY_NODETb4b4b4, Te6edf3reboot_count Tff7b72= T79c0ff3Tb4b4b4)Tb4b4b4)Tb4b4b4,
Te6edf3FromRadioTb4b4b4(Te6edf3metadata Tff7b72= Te6edf3metadataTb4b4b4)Tb4b4b4,
Te6edf3FromRadioTb4b4b4(Te6edf3config Tff7b72= Te6edf3ConfigTb4b4b4(Te6edf3lora Tff7b72= Te6edf3defaultLoRaConfigTb4b4b4)Tb4b4b4)Tb4b4b4,
Te6edf3FromRadioTb4b4b4(Te6edf3config Tff7b72= Te6edf3ConfigTb4b4b4(Te6edf3device Tff7b72= Te6edf3ConfigTb4b4b4.Te6edf3DeviceConfigTb4b4b4(Te6edf3role Tff7b72= Te6edf3ConfigTb4b4b4.Te6edf3DeviceConfigTb4b4b4.Te6edf3RoleTb4b4b4.Te6edf3CLIENTTb4b4b4)Tb4b4b4)Tb4b4b4)Tb4b4b4,
Te6edf3FromRadioTb4b4b4(
Te6edf3config Tff7b72=
Te6edf3ConfigTb4b4b4(
Te6edf3position Tff7b72=
Te6edf3ConfigTb4b4b4.Te6edf3PositionConfigTb4b4b4(
Te6edf3position_broadcast_secs Tff7b72= T79c0ff9T79c0ff0T79c0ff0Tb4b4b4,
Te6edf3position_broadcast_smart_enabled Tff7b72= Tff7b72trueTb4b4b4,
Te6edf3gps_enabled Tff7b72= Tff7b72trueTb4b4b4,
Tb4b4b4)Tb4b4b4,
Tb4b4b4)Tb4b4b4,
Tb4b4b4)Tb4b4b4,
Te6edf3FromRadioTb4b4b4(Te6edf3channel Tff7b72= Te6edf3defaultChannelTb4b4b4)Tb4b4b4,
Te6edf3FromRadioTb4b4b4(Te6edf3config_complete_id Tff7b72= Te6edf3HandshakeConstantsTb4b4b4.Te6edf3CONFIG_NONCETb4b4b4)Tb4b4b4,
Tb4b4b4)
Te6edf3framesTb4b4b4.Te6edf3forEach Tb4b4b4{ Te6edf3callbackTb4b4b4.Te6edf3handleFromRadioTb4b4b4(Tffa657itTb4b4b4.Te6edf3encodeTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4}
Tb4b4b4}

T8b949e/** Stage 2: the node database. This is the only window in which the app accepts `node_info`. */
Tff7b72private Tff7b72fun Td2a8ffsendNodeInfoStageTb4b4b4(Tb4b4b4) Tb4b4b4{
Te6edf3LoggerTb4b4b4.Te6edf3d Tb4b4b4{ Ta5d6ff"Ta5d6ffMock transport answering node-info stage with Tffd700${Te6edf3SIM_PEERSTb4b4b4.Te6edf3size Tff7b72+ T79c0ff1Tffd700}Ta5d6ff nodesTa5d6ff" Tb4b4b4}
Te6edf3callbackTb4b4b4.Te6edf3handleFromRadioTb4b4b4(Te6edf3FromRadioTb4b4b4(Te6edf3node_info Tff7b72= Te6edf3localNodeInfoTb4b4b4(Tb4b4b4)Tb4b4b4)Tb4b4b4.Te6edf3encodeTb4b4b4(Tb4b4b4)Tb4b4b4)
Te6edf3SIM_PEERSTb4b4b4.Te6edf3forEach Tb4b4b4{ Te6edf3peer Tff7b72-Tff7b72> Te6edf3callbackTb4b4b4.Te6edf3handleFromRadioTb4b4b4(Te6edf3FromRadioTb4b4b4(Te6edf3node_info Tff7b72= Te6edf3peerTb4b4b4.Te6edf3toNodeInfoTb4b4b4(Tb4b4b4)Tb4b4b4)Tb4b4b4.Te6edf3encodeTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4}
Te6edf3callbackTb4b4b4.Te6edf3handleFromRadioTb4b4b4(Te6edf3FromRadioTb4b4b4(Te6edf3config_complete_id Tff7b72= Te6edf3HandshakeConstantsTb4b4b4.Te6edf3NODE_INFO_NONCETb4b4b4)Tb4b4b4.Te6edf3encodeTb4b4b4(Tb4b4b4)Tb4b4b4)

Tff7b72if Tb4b4b4(Te6edf3trafficStartedTb4b4b4.Te6edf3compareAndSetTb4b4b4(Te6edf3expect Tff7b72= Tff7b72falseTb4b4b4, Te6edf3update Tff7b72= Tff7b72trueTb4b4b4)Tb4b4b4) Tb4b4b4{
Te6edf3transportScopeTb4b4b4.Te6edf3handledLaunch Tb4b4b4{ Te6edf3seedTrafficTb4b4b4(Tb4b4b4) Tb4b4b4}
Tb4b4b4}
Tb4b4b4}

Tff7b72private Tff7b72fun Td2a8fflocalNodeInfoTb4b4b4(Tb4b4b4) Tff7b72= Te6edf3NodeInfoTb4b4b4(
Te6edf3num Tff7b72= Te6edf3MY_NODETb4b4b4,
Te6edf3last_heard Tff7b72= Te6edf3nowSecondsTb4b4b4.Te6edf3toIntTb4b4b4(Tb4b4b4)Tb4b4b4,
Te6edf3user Tff7b72=
Te6edf3UserTb4b4b4(
Te6edf3id Tff7b72= Te6edf3NodeAddressTb4b4b4.Te6edf3numToDefaultIdTb4b4b4(Te6edf3MY_NODETb4b4b4)Tb4b4b4,
Te6edf3long_name Tff7b72= Ta5d6ff"Ta5d6ffDemo HandsetTa5d6ff"Tb4b4b4,
Te6edf3short_name Tff7b72= Ta5d6ff"Ta5d6ffDEMOTa5d6ff"Tb4b4b4,
Te6edf3hw_model Tff7b72= Te6edf3HardwareModelTb4b4b4.Te6edf3ANDROID_SIMTb4b4b4,
Te6edf3role Tff7b72= Te6edf3ConfigTb4b4b4.Te6edf3DeviceConfigTb4b4b4.Te6edf3RoleTb4b4b4.Te6edf3CLIENTTb4b4b4,
Tb4b4b4)Tb4b4b4,
Te6edf3position Tff7b72= Te6edf3MY_POSITIONTb4b4b4.Te6edf3toProtoTb4b4b4(Tb4b4b4)Tb4b4b4,
Te6edf3device_metrics Tff7b72=
Te6edf3DeviceMetricsTb4b4b4(
Te6edf3battery_level Tff7b72= T79c0ff7T79c0ff8Tb4b4b4,
Te6edf3voltage Tff7b72= T79c0ff3.98fTb4b4b4,
Te6edf3channel_utilization Tff7b72= T79c0ff8.4fTb4b4b4,
Te6edf3air_util_tx Tff7b72= T79c0ff1.9fTb4b4b4,
Te6edf3uptime_seconds Tff7b72= T79c0ff7Te6edf3_240Tb4b4b4,
Tb4b4b4)Tb4b4b4,
Te6edf3hops_away Tff7b72= T79c0ff0Tb4b4b4,
Tb4b4b4)

Tff7b72private Tff7b72fun Te6edf3SimPeerTb4b4b4.Td2a8fftoNodeInfoTb4b4b4(Tb4b4b4) Tff7b72= Te6edf3NodeInfoTb4b4b4(
Te6edf3num Tff7b72= Te6edf3numTb4b4b4,
T8b949e// Without last_heard every simulated node reads as offline and disappears the moment the user turns on the
T8b949e// node list's "online only" filter.
Te6edf3last_heard Tff7b72= Te6edf3nowSecondsTb4b4b4.Te6edf3toIntTb4b4b4(Tb4b4b4) Tff7b72- Te6edf3secondsSinceHeardTb4b4b4,
Te6edf3user Tff7b72=
Te6edf3UserTb4b4b4(
Te6edf3id Tff7b72= Te6edf3NodeAddressTb4b4b4.Te6edf3numToDefaultIdTb4b4b4(Te6edf3numTb4b4b4)Tb4b4b4,
Te6edf3long_name Tff7b72= Te6edf3longNameTb4b4b4,
Te6edf3short_name Tff7b72= Te6edf3shortNameTb4b4b4,
Te6edf3hw_model Tff7b72= Te6edf3hwModelTb4b4b4,
Te6edf3role Tff7b72= Te6edf3roleTb4b4b4,
Tb4b4b4)Tb4b4b4,
Te6edf3position Tff7b72= Te6edf3SimPositionTb4b4b4(Te6edf3latitudeTb4b4b4, Te6edf3longitudeTb4b4b4, Te6edf3altitudeTb4b4b4)Tb4b4b4.Te6edf3toProtoTb4b4b4(Tb4b4b4)Tb4b4b4,
Te6edf3device_metrics Tff7b72=
Te6edf3DeviceMetricsTb4b4b4(Te6edf3battery_level Tff7b72= Te6edf3batteryLevelTb4b4b4, Te6edf3voltage Tff7b72= Te6edf3voltageTb4b4b4, Te6edf3uptime_seconds Tff7b72= Te6edf3uptimeSecondsTb4b4b4)Tb4b4b4,
Te6edf3snr Tff7b72= Te6edf3snrTb4b4b4,
Te6edf3hops_away Tff7b72= Te6edf3hopsTb4b4b4,
Te6edf3channel Tff7b72= T79c0ff0Tb4b4b4,
Tb4b4b4)

T8b949e// ── Simulated traffic ─────────────────────────────────────────────────────────────────────

T8b949e/**
* Seeds a mesh that already has history, then keeps it gently alive.
*
* The seed pass is spaced out because message and telemetry rows are timestamped when the app persists them, not
* from the payload: a burst emitted inside the same millisecond collapses into one indistinguishable clump of chart
* points and messages.
*/
Tff7b72private Tff7b72suspend Tff7b72fun Td2a8ffseedTrafficTb4b4b4(Tb4b4b4) Tb4b4b4{
Te6edf3SIM_PEERSTb4b4b4.Te6edf3forEach Tb4b4b4{ Te6edf3peer Tff7b72-Tff7b72>
Te6edf3lifecycleTb4b4b4.Te6edf3runIfOpen Tb4b4b4{ Te6edf3callbackTb4b4b4.Te6edf3handleFromRadioTb4b4b4(Te6edf3peerTb4b4b4.Te6edf3positionPacketTb4b4b4(Te6edf3nextPacketIdTb4b4b4(Tb4b4b4)Tb4b4b4)Tb4b4b4.Te6edf3encodeTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4}
Te6edf3delayTb4b4b4(Te6edf3SEED_SPACING_MSTb4b4b4)
Tb4b4b4}

Te6edf3SIM_PEERSTb4b4b4.Te6edf3takeTb4b4b4(Te6edf3TELEMETRY_PEER_COUNTTb4b4b4)Tb4b4b4.Te6edf3forEach Tb4b4b4{ Te6edf3peer Tff7b72-Tff7b72>
Te6edf3lifecycleTb4b4b4.Te6edf3runIfOpen Tb4b4b4{
Te6edf3callbackTb4b4b4.Te6edf3handleFromRadioTb4b4b4(Te6edf3peerTb4b4b4.Te6edf3deviceTelemetryPacketTb4b4b4(Te6edf3nextPacketIdTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3tick Tff7b72= T79c0ff0Tb4b4b4)Tb4b4b4.Te6edf3encodeTb4b4b4(Tb4b4b4)Tb4b4b4)
Tb4b4b4}
Te6edf3delayTb4b4b4(Te6edf3SEED_SPACING_MSTb4b4b4)
Tb4b4b4}

Te6edf3WEATHER_PEER_INDEXESTb4b4b4.Te6edf3forEach Tb4b4b4{ Te6edf3index Tff7b72-Tff7b72>
Tff7b72val Te6edf3peer Tff7b72= Te6edf3SIM_PEERSTff7b72[Te6edf3indexTff7b72]
Te6edf3lifecycleTb4b4b4.Te6edf3runIfOpen Tb4b4b4{
Te6edf3callbackTb4b4b4.Te6edf3handleFromRadioTb4b4b4(Te6edf3peerTb4b4b4.Te6edf3environmentTelemetryPacketTb4b4b4(Te6edf3nextPacketIdTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3tick Tff7b72= T79c0ff0Tb4b4b4)Tb4b4b4.Te6edf3encodeTb4b4b4(Tb4b4b4)Tb4b4b4)
Tb4b4b4}
Te6edf3delayTb4b4b4(Te6edf3SEED_SPACING_MSTb4b4b4)
Tb4b4b4}

Te6edf3lifecycleTb4b4b4.Te6edf3runIfOpen Tb4b4b4{ Te6edf3callbackTb4b4b4.Te6edf3handleFromRadioTb4b4b4(Te6edf3SIM_PEERSTff7b72[T79c0ff0Tff7b72]Tb4b4b4.Te6edf3neighborInfoPacketTb4b4b4(Te6edf3nextPacketIdTb4b4b4(Tb4b4b4)Tb4b4b4)Tb4b4b4.Te6edf3encodeTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4}
Te6edf3delayTb4b4b4(Te6edf3SEED_SPACING_MSTb4b4b4)
Te6edf3lifecycleTb4b4b4.Te6edf3runIfOpen Tb4b4b4{ Te6edf3callbackTb4b4b4.Te6edf3handleFromRadioTb4b4b4(Te6edf3SIM_PEERSTff7b72[T79c0ff1Tff7b72]Tb4b4b4.Te6edf3nodeStatusPacketTb4b4b4(Te6edf3nextPacketIdTb4b4b4(Tb4b4b4)Tb4b4b4)Tb4b4b4.Te6edf3encodeTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4}
Te6edf3delayTb4b4b4(Te6edf3SEED_SPACING_MSTb4b4b4)

T8b949e// Each message is stamped progressively closer to now, so the thread reads as a conversation that unfolded over
T8b949e// the last while rather than a block of messages that all arrived in the same second.
Te6edf3CHANNEL_CONVERSATIONTb4b4b4.Te6edf3forEachIndexed Tb4b4b4{ Te6edf3indexTb4b4b4, Tb4b4b4(Te6edf3peerIndexTb4b4b4, Te6edf3textTb4b4b4) Tff7b72-Tff7b72>
Tff7b72val Te6edf3peer Tff7b72= Te6edf3SIM_PEERSTff7b72[Te6edf3peerIndexTff7b72]
Te6edf3lifecycleTb4b4b4.Te6edf3runIfOpen Tb4b4b4{
Te6edf3callbackTb4b4b4.Te6edf3handleFromRadioTb4b4b4(
Te6edf3peer
Tb4b4b4.Te6edf3textPacketTb4b4b4(
Te6edf3id Tff7b72= Te6edf3nextPacketIdTb4b4b4(Tb4b4b4)Tb4b4b4,
Te6edf3to Tff7b72= Te6edf3BROADCAST_ADDRTb4b4b4,
Te6edf3text Tff7b72= Te6edf3textTb4b4b4,
Te6edf3ageSeconds Tff7b72= Te6edf3messageAgeSecondsTb4b4b4(Te6edf3CHANNEL_CONVERSATIONTb4b4b4.Te6edf3sizeTb4b4b4, Te6edf3indexTb4b4b4)Tb4b4b4,
Tb4b4b4)
Tb4b4b4.Te6edf3encodeTb4b4b4(Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Tb4b4b4}
Te6edf3delayTb4b4b4(Te6edf3SEED_SPACING_MSTb4b4b4)
Tb4b4b4}

Te6edf3DIRECT_CONVERSATIONTb4b4b4.Te6edf3forEachIndexed Tb4b4b4{ Te6edf3indexTb4b4b4, Te6edf3text Tff7b72-Tff7b72>
Te6edf3lifecycleTb4b4b4.Te6edf3runIfOpen Tb4b4b4{
Te6edf3callbackTb4b4b4.Te6edf3handleFromRadioTb4b4b4(
Te6edf3SIM_PEERSTff7b72[Te6edf3DIRECT_PEER_INDEXTff7b72]Tb4b4b4.Te6edf3textPacketTb4b4b4(
Te6edf3id Tff7b72= Te6edf3nextPacketIdTb4b4b4(Tb4b4b4)Tb4b4b4,
Te6edf3to Tff7b72= Te6edf3MY_NODETb4b4b4,
Te6edf3text Tff7b72= Te6edf3textTb4b4b4,
Te6edf3ageSeconds Tff7b72= Te6edf3messageAgeSecondsTb4b4b4(Te6edf3DIRECT_CONVERSATIONTb4b4b4.Te6edf3sizeTb4b4b4, Te6edf3indexTb4b4b4)Tb4b4b4,
Tb4b4b4)
Tb4b4b4.Te6edf3encodeTb4b4b4(Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Tb4b4b4}
Te6edf3delayTb4b4b4(Te6edf3SEED_SPACING_MSTb4b4b4)
Tb4b4b4}

Te6edf3streamLiveTelemetryTb4b4b4(Tb4b4b4)
Tb4b4b4}

T8b949e/** How long ago the message at [index] of a [count]-message seeded thread was "received". Oldest first. */
Tff7b72private Tff7b72fun Td2a8ffmessageAgeSecondsTb4b4b4(Te6edf3countTb4b4b4: Tffa657IntTb4b4b4, Te6edf3indexTb4b4b4: Tffa657IntTb4b4b4) Tff7b72= Tb4b4b4(Te6edf3count Tff7b72- Te6edf3indexTb4b4b4) Tff7b72* Te6edf3MESSAGE_SPACING_SECONDS

T8b949e/**
* Keeps the demo mesh breathing: one peer reports in per tick, so the telemetry charts gain points with distinct
* timestamps and the node list's "last heard" values stay fresh while the reviewer explores.
*/
Tff7b72private Tff7b72suspend Tff7b72fun Td2a8ffstreamLiveTelemetryTb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72var Te6edf3tick Tff7b72= T79c0ff1
Tff7b72while Tb4b4b4(Tff7b72trueTb4b4b4) Tb4b4b4{
Te6edf3delayTb4b4b4(Te6edf3LIVE_TICK_MSTb4b4b4)
Tff7b72val Te6edf3peer Tff7b72= Te6edf3SIM_PEERSTff7b72[Te6edf3tick Tff7b72% Te6edf3TELEMETRY_PEER_COUNTTff7b72]
Te6edf3lifecycleTb4b4b4.Te6edf3runIfOpen Tb4b4b4{ Te6edf3callbackTb4b4b4.Te6edf3handleFromRadioTb4b4b4(Te6edf3peerTb4b4b4.Te6edf3deviceTelemetryPacketTb4b4b4(Te6edf3nextPacketIdTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3tickTb4b4b4)Tb4b4b4.Te6edf3encodeTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4}
Tff7b72if Tb4b4b4(Te6edf3tick Tff7b72% Te6edf3WEATHER_TICK_INTERVAL Tff7b72=Tff7b72= T79c0ff0Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3weatherPeer Tff7b72= Te6edf3SIM_PEERSTff7b72[Te6edf3WEATHER_PEER_INDEXESTb4b4b4.Te6edf3firstTb4b4b4(Tb4b4b4)Tff7b72]
Te6edf3lifecycleTb4b4b4.Te6edf3runIfOpen Tb4b4b4{
Te6edf3callbackTb4b4b4.Te6edf3handleFromRadioTb4b4b4(Te6edf3weatherPeerTb4b4b4.Te6edf3environmentTelemetryPacketTb4b4b4(Te6edf3nextPacketIdTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3tickTb4b4b4)Tb4b4b4.Te6edf3encodeTb4b4b4(Tb4b4b4)Tb4b4b4)
Tb4b4b4}
Tb4b4b4}
Te6edf3tickTff7b72+Tff7b72+
Tb4b4b4}
Tb4b4b4}

T8b949e/**
* Answers a text the user just sent, so the demo has a two-way conversation rather than a wall of inbound messages.
* A broadcast gets a reply on the same channel; a direct message gets a direct reply from its addressee.
*/
Tff7b72private Tff7b72fun Td2a8ffsendSimulatedReplyTb4b4b4(Te6edf3packetTb4b4b4: Te6edf3MeshPacket?Tb4b4b4) Tb4b4b4{
Tff7b72if Tb4b4b4(Te6edf3packet Tff7b72=Tff7b72= Tff7b72nullTb4b4b4) Tff7b72return
Tff7b72val Te6edf3isBroadcast Tff7b72= Te6edf3packetTb4b4b4.Te6edf3to Tff7b72=Tff7b72= Te6edf3BROADCAST_ADDR
Tff7b72val Te6edf3responder Tff7b72=
Tff7b72if Tb4b4b4(Te6edf3isBroadcastTb4b4b4) Tb4b4b4{
Te6edf3SIM_PEERSTff7b72[Te6edf3DIRECT_PEER_INDEXTff7b72]
Tb4b4b4} Tff7b72else Tb4b4b4{
Te6edf3SIM_PEERSTb4b4b4.Te6edf3firstOrNull Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3num Tff7b72=Tff7b72= Te6edf3packetTb4b4b4.Te6edf3to Tb4b4b4} Tff7b72?: Tff7b72return
Tb4b4b4}
Tff7b72val Te6edf3replyTo Tff7b72= Tff7b72if Tb4b4b4(Te6edf3isBroadcastTb4b4b4) Te6edf3BROADCAST_ADDR Tff7b72else Te6edf3MY_NODE

Te6edf3transportScopeTb4b4b4.Te6edf3handledLaunch Tb4b4b4{
Te6edf3delayTb4b4b4(Te6edf3REPLY_DELAY_MSTb4b4b4)
Te6edf3lifecycleTb4b4b4.Te6edf3runIfOpen Tb4b4b4{
Te6edf3callbackTb4b4b4.Te6edf3handleFromRadioTb4b4b4(
Te6edf3responder
Tb4b4b4.Te6edf3textPacketTb4b4b4(Te6edf3id Tff7b72= Te6edf3nextPacketIdTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3to Tff7b72= Te6edf3replyToTb4b4b4, Te6edf3text Tff7b72= Te6edf3AUTO_REPLY_TEXTTb4b4b4, Te6edf3ageSeconds Tff7b72= T79c0ff0Tb4b4b4)
Tb4b4b4.Te6edf3encodeTb4b4b4(Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}

T8b949e// ── Packet builders ──────────────────────────────────────────────────────────────────────

T8b949e/**
* Base envelope for a packet "received over the air" from [SimPeer].
*
* `transport_mechanism` and the matching `hop_start`/`hop_limit` are load-bearing, not decoration: the app only
* harvests SNR, RSSI and hop count from packets that pass its direct-LoRa test.
*/
Tff7b72private Tff7b72fun Te6edf3SimPeerTb4b4b4.Td2a8ffpacketTb4b4b4(Te6edf3idTb4b4b4: Tffa657IntTb4b4b4, Te6edf3toTb4b4b4: Tffa657IntTb4b4b4, Te6edf3ageSecondsTb4b4b4: Tffa657IntTb4b4b4, Te6edf3dataTb4b4b4: Te6edf3DataTb4b4b4) Tff7b72= Te6edf3MeshPacketTb4b4b4(
Te6edf3id Tff7b72= Te6edf3idTb4b4b4,
Te6edf3from Tff7b72= Te6edf3numTb4b4b4,
Te6edf3to Tff7b72= Te6edf3toTb4b4b4,
Te6edf3channel Tff7b72= T79c0ff0Tb4b4b4,
Te6edf3rx_time Tff7b72= Tb4b4b4(Te6edf3nowSeconds Tff7b72- Te6edf3ageSecondsTb4b4b4)Tb4b4b4.Te6edf3toIntTb4b4b4(Tb4b4b4)Tb4b4b4,
Te6edf3rx_snr Tff7b72= Te6edf3snrTb4b4b4,
Te6edf3rx_rssi Tff7b72= Te6edf3rssiTb4b4b4,
Te6edf3hop_start Tff7b72= Te6edf3DEFAULT_HOP_STARTTb4b4b4,
Te6edf3hop_limit Tff7b72= Te6edf3DEFAULT_HOP_START Tff7b72- Te6edf3hopsTb4b4b4,
Te6edf3transport_mechanism Tff7b72= Te6edf3MeshPacketTb4b4b4.Te6edf3TransportMechanismTb4b4b4.Te6edf3TRANSPORT_LORATb4b4b4,
Te6edf3decoded Tff7b72= Te6edf3dataTb4b4b4,
Tb4b4b4)

Tff7b72private Tff7b72fun Te6edf3SimPeerTb4b4b4.Td2a8fftextPacketTb4b4b4(Te6edf3idTb4b4b4: Tffa657IntTb4b4b4, Te6edf3toTb4b4b4: Tffa657IntTb4b4b4, Te6edf3textTb4b4b4: Tffa657StringTb4b4b4, Te6edf3ageSecondsTb4b4b4: Tffa657IntTb4b4b4) Tff7b72= Te6edf3FromRadioTb4b4b4(
Te6edf3packet Tff7b72=
Te6edf3packetTb4b4b4(
Te6edf3id Tff7b72= Te6edf3idTb4b4b4,
Te6edf3to Tff7b72= Te6edf3toTb4b4b4,
Te6edf3ageSeconds Tff7b72= Te6edf3ageSecondsTb4b4b4,
Te6edf3data Tff7b72= Te6edf3DataTb4b4b4(Te6edf3portnum Tff7b72= Te6edf3PortNumTb4b4b4.Te6edf3TEXT_MESSAGE_APPTb4b4b4, Te6edf3payload Tff7b72= Te6edf3textTb4b4b4.Te6edf3encodeUtf8Tb4b4b4(Tb4b4b4)Tb4b4b4)Tb4b4b4,
Tb4b4b4)Tb4b4b4,
Tb4b4b4)

Tff7b72private Tff7b72fun Te6edf3SimPeerTb4b4b4.Td2a8ffpositionPacketTb4b4b4(Te6edf3idTb4b4b4: Tffa657IntTb4b4b4) Tff7b72= Te6edf3FromRadioTb4b4b4(
Te6edf3packet Tff7b72=
Te6edf3packetTb4b4b4(
Te6edf3id Tff7b72= Te6edf3idTb4b4b4,
Te6edf3to Tff7b72= Te6edf3BROADCAST_ADDRTb4b4b4,
Te6edf3ageSeconds Tff7b72= T79c0ff0Tb4b4b4,
Te6edf3data Tff7b72=
Te6edf3DataTb4b4b4(
Te6edf3portnum Tff7b72= Te6edf3PortNumTb4b4b4.Te6edf3POSITION_APPTb4b4b4,
Te6edf3payload Tff7b72= Te6edf3SimPositionTb4b4b4(Te6edf3latitudeTb4b4b4, Te6edf3longitudeTb4b4b4, Te6edf3altitudeTb4b4b4)Tb4b4b4.Te6edf3toProtoTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3encodeTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3toByteStringTb4b4b4(Tb4b4b4)Tb4b4b4,
Tb4b4b4)Tb4b4b4,
Tb4b4b4)Tb4b4b4,
Tb4b4b4)

T8b949e/**
* Device telemetry for [tick], drifted so the charts show a trend rather than a flat line.
*
* Both the percentage and the voltage are clamped, not just the percentage: a tick is 20s, so an unbounded slope
* takes the voltage through 0V inside a couple of hours and the battery and telemetry views then render a cell that
* cannot physically exist.
*/
Tff7b72private Tff7b72fun Te6edf3SimPeerTb4b4b4.Td2a8ffdeviceTelemetryPacketTb4b4b4(Te6edf3idTb4b4b4: Tffa657IntTb4b4b4, Te6edf3tickTb4b4b4: Tffa657IntTb4b4b4)Tb4b4b4: Te6edf3FromRadio Tb4b4b4{
Tff7b72val Te6edf3driftedBattery Tff7b72= Tb4b4b4(Te6edf3batteryLevel Tff7b72- Te6edf3tickTb4b4b4)Tb4b4b4.Te6edf3coerceInTb4b4b4(Te6edf3MIN_BATTERY_PERCENTTb4b4b4, Te6edf3MAX_BATTERY_PERCENTTb4b4b4)
Tff7b72val Te6edf3driftedVoltage Tff7b72= Tb4b4b4(Te6edf3voltage Tff7b72- Te6edf3tick Tff7b72* Te6edf3VOLTAGE_DRIFT_PER_TICKTb4b4b4)Tb4b4b4.Te6edf3coerceAtLeastTb4b4b4(Te6edf3MIN_CELL_VOLTAGETb4b4b4)
Tff7b72return Te6edf3FromRadioTb4b4b4(
Te6edf3packet Tff7b72=
Te6edf3packetTb4b4b4(
Te6edf3id Tff7b72= Te6edf3idTb4b4b4,
Te6edf3to Tff7b72= Te6edf3BROADCAST_ADDRTb4b4b4,
Te6edf3ageSeconds Tff7b72= T79c0ff0Tb4b4b4,
Te6edf3data Tff7b72=
Te6edf3DataTb4b4b4(
Te6edf3portnum Tff7b72= Te6edf3PortNumTb4b4b4.Te6edf3TELEMETRY_APPTb4b4b4,
Te6edf3payload Tff7b72=
Te6edf3TelemetryTb4b4b4(
Te6edf3device_metrics Tff7b72=
Te6edf3DeviceMetricsTb4b4b4(
Te6edf3battery_level Tff7b72= Te6edf3driftedBatteryTb4b4b4,
Te6edf3voltage Tff7b72= Te6edf3driftedVoltageTb4b4b4,
Te6edf3channel_utilization Tff7b72= T79c0ff6f Tff7b72+ Tb4b4b4(Te6edf3tick Tff7b72% T79c0ff5Tb4b4b4) Tff7b72* T79c0ff1.5fTb4b4b4,
Te6edf3air_util_tx Tff7b72= T79c0ff1.2f Tff7b72+ Tb4b4b4(Te6edf3tick Tff7b72% T79c0ff4Tb4b4b4) Tff7b72* T79c0ff0.4fTb4b4b4,
Te6edf3uptime_seconds Tff7b72= Te6edf3uptimeSeconds Tff7b72+ Te6edf3tick Tff7b72* Tb4b4b4(Te6edf3LIVE_TICK_MS Tff7b72/ T79c0ff1T79c0ff0T79c0ff0T79c0ff0Tb4b4b4)Tb4b4b4.Te6edf3toIntTb4b4b4(Tb4b4b4)Tb4b4b4,
Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Tb4b4b4.Te6edf3encodeTb4b4b4(Tb4b4b4)
Tb4b4b4.Te6edf3toByteStringTb4b4b4(Tb4b4b4)Tb4b4b4,
Tb4b4b4)Tb4b4b4,
Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Tb4b4b4}

Tff7b72private Tff7b72fun Te6edf3SimPeerTb4b4b4.Td2a8ffenvironmentTelemetryPacketTb4b4b4(Te6edf3idTb4b4b4: Tffa657IntTb4b4b4, Te6edf3tickTb4b4b4: Tffa657IntTb4b4b4) Tff7b72= Te6edf3FromRadioTb4b4b4(
Te6edf3packet Tff7b72=
Te6edf3packetTb4b4b4(
Te6edf3id Tff7b72= Te6edf3idTb4b4b4,
Te6edf3to Tff7b72= Te6edf3BROADCAST_ADDRTb4b4b4,
Te6edf3ageSeconds Tff7b72= T79c0ff0Tb4b4b4,
Te6edf3data Tff7b72=
Te6edf3DataTb4b4b4(
Te6edf3portnum Tff7b72= Te6edf3PortNumTb4b4b4.Te6edf3TELEMETRY_APPTb4b4b4,
Te6edf3payload Tff7b72=
Te6edf3TelemetryTb4b4b4(
Te6edf3environment_metrics Tff7b72=
Te6edf3EnvironmentMetricsTb4b4b4(
T8b949e// Temperature AND humidity must both be present or the Environment tab
T8b949e// stays empty.
Te6edf3temperature Tff7b72= T79c0ff1T79c0ff8.5f Tff7b72+ Tb4b4b4(Te6edf3tick Tff7b72% T79c0ff7Tb4b4b4) Tff7b72* T79c0ff0.4fTb4b4b4,
Te6edf3relative_humidity Tff7b72= T79c0ff4T79c0ff7f Tff7b72+ Tb4b4b4(Te6edf3tick Tff7b72% T79c0ff5Tb4b4b4) Tff7b72* T79c0ff1.5fTb4b4b4,
Te6edf3barometric_pressure Tff7b72= T79c0ff1T79c0ff0T79c0ff1T79c0ff3.2f Tff7b72+ Tb4b4b4(Te6edf3tick Tff7b72% T79c0ff3Tb4b4b4) Tff7b72* T79c0ff0.3fTb4b4b4,
Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Tb4b4b4.Te6edf3encodeTb4b4b4(Tb4b4b4)
Tb4b4b4.Te6edf3toByteStringTb4b4b4(Tb4b4b4)Tb4b4b4,
Tb4b4b4)Tb4b4b4,
Tb4b4b4)Tb4b4b4,
Tb4b4b4)

Tff7b72private Tff7b72fun Te6edf3SimPeerTb4b4b4.Td2a8ffneighborInfoPacketTb4b4b4(Te6edf3idTb4b4b4: Tffa657IntTb4b4b4) Tff7b72= Te6edf3FromRadioTb4b4b4(
Te6edf3packet Tff7b72=
Te6edf3packetTb4b4b4(
Te6edf3id Tff7b72= Te6edf3idTb4b4b4,
Te6edf3to Tff7b72= Te6edf3BROADCAST_ADDRTb4b4b4,
Te6edf3ageSeconds Tff7b72= T79c0ff0Tb4b4b4,
Te6edf3data Tff7b72=
Te6edf3DataTb4b4b4(
Te6edf3portnum Tff7b72= Te6edf3PortNumTb4b4b4.Te6edf3NEIGHBORINFO_APPTb4b4b4,
Te6edf3payload Tff7b72=
Te6edf3NeighborInfoTb4b4b4(
Te6edf3node_id Tff7b72= Te6edf3numTb4b4b4,
Te6edf3last_sent_by_id Tff7b72= Te6edf3numTb4b4b4,
Te6edf3node_broadcast_interval_secs Tff7b72= T79c0ff9T79c0ff0T79c0ff0Tb4b4b4,
Te6edf3neighbors Tff7b72=
Te6edf3SIM_PEERSTb4b4b4.Te6edf3dropTb4b4b4(T79c0ff1Tb4b4b4)Tb4b4b4.Te6edf3takeTb4b4b4(T79c0ff3Tb4b4b4)Tb4b4b4.Te6edf3map Tb4b4b4{ Te6edf3neighbor Tff7b72-Tff7b72>
Te6edf3NeighborTb4b4b4(
Te6edf3node_id Tff7b72= Te6edf3neighborTb4b4b4.Te6edf3numTb4b4b4,
Te6edf3snr Tff7b72= Te6edf3neighborTb4b4b4.Te6edf3snrTb4b4b4,
Te6edf3last_rx_time Tff7b72= Te6edf3nowSecondsTb4b4b4.Te6edf3toIntTb4b4b4(Tb4b4b4)Tb4b4b4,
Te6edf3node_broadcast_interval_secs Tff7b72= T79c0ff9T79c0ff0T79c0ff0Tb4b4b4,
Tb4b4b4)
Tb4b4b4}Tb4b4b4,
Tb4b4b4)
Tb4b4b4.Te6edf3encodeTb4b4b4(Tb4b4b4)
Tb4b4b4.Te6edf3toByteStringTb4b4b4(Tb4b4b4)Tb4b4b4,
Tb4b4b4)Tb4b4b4,
Tb4b4b4)Tb4b4b4,
Tb4b4b4)

Tff7b72private Tff7b72fun Te6edf3SimPeerTb4b4b4.Td2a8ffnodeStatusPacketTb4b4b4(Te6edf3idTb4b4b4: Tffa657IntTb4b4b4) Tff7b72= Te6edf3FromRadioTb4b4b4(
Te6edf3packet Tff7b72=
Te6edf3packetTb4b4b4(
Te6edf3id Tff7b72= Te6edf3idTb4b4b4,
Te6edf3to Tff7b72= Te6edf3BROADCAST_ADDRTb4b4b4,
Te6edf3ageSeconds Tff7b72= T79c0ff0Tb4b4b4,
Te6edf3data Tff7b72=
Te6edf3DataTb4b4b4(
Te6edf3portnum Tff7b72= Te6edf3PortNumTb4b4b4.Te6edf3NODE_STATUS_APPTb4b4b4,
Te6edf3payload Tff7b72= Te6edf3StatusMessageTb4b4b4(Te6edf3status Tff7b72= Te6edf3PEER_NODE_STATUSTb4b4b4)Tb4b4b4.Te6edf3encodeTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3toByteStringTb4b4b4(Tb4b4b4)Tb4b4b4,
Tb4b4b4)Tb4b4b4,
Tb4b4b4)Tb4b4b4,
Tb4b4b4)

Tff7b72private Tff7b72fun Td2a8ffmakeDataPacketTb4b4b4(Te6edf3fromInTb4b4b4: Tffa657IntTb4b4b4, Te6edf3toInTb4b4b4: Tffa657IntTb4b4b4, Te6edf3dataTb4b4b4: Te6edf3DataTb4b4b4) Tff7b72= Te6edf3FromRadioTb4b4b4(
Te6edf3packet Tff7b72=
Te6edf3MeshPacketTb4b4b4(
Te6edf3id Tff7b72= Te6edf3nextPacketIdTb4b4b4(Tb4b4b4)Tb4b4b4,
Te6edf3from Tff7b72= Te6edf3fromInTb4b4b4,
Te6edf3to Tff7b72= Te6edf3toInTb4b4b4,
Te6edf3rx_time Tff7b72= Te6edf3nowSecondsTb4b4b4.Te6edf3toIntTb4b4b4(Tb4b4b4)Tb4b4b4,
Te6edf3rx_snr Tff7b72= T79c0ff1.5fTb4b4b4,
Te6edf3decoded Tff7b72= Te6edf3dataTb4b4b4,
Tb4b4b4)Tb4b4b4,
Tb4b4b4)

Tff7b72private Tff7b72fun Td2a8ffmakeAckTb4b4b4(Te6edf3fromInTb4b4b4: Tffa657IntTb4b4b4, Te6edf3toInTb4b4b4: Tffa657IntTb4b4b4, Te6edf3msgIdTb4b4b4: Tffa657IntTb4b4b4) Tff7b72= Te6edf3makeDataPacketTb4b4b4(
Te6edf3fromInTb4b4b4,
Te6edf3toInTb4b4b4,
Te6edf3DataTb4b4b4(Te6edf3portnum Tff7b72= Te6edf3PortNumTb4b4b4.Te6edf3ROUTING_APPTb4b4b4, Te6edf3payload Tff7b72= Te6edf3RoutingTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3encodeTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3toByteStringTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3request_id Tff7b72= Te6edf3msgIdTb4b4b4)Tb4b4b4,
Tb4b4b4)

Tff7b72private Tff7b72fun Td2a8ffsendQueueStatusTb4b4b4(Te6edf3msgIdTb4b4b4: Tffa657IntTb4b4b4) Tff7b72= Te6edf3callbackTb4b4b4.Te6edf3handleFromRadioTb4b4b4(
Te6edf3FromRadioTb4b4b4(Te6edf3queueStatus Tff7b72= Te6edf3QueueStatusTb4b4b4(Te6edf3res Tff7b72= T79c0ff0Tb4b4b4, Te6edf3free Tff7b72= T79c0ff1T79c0ff6Tb4b4b4, Te6edf3mesh_packet_id Tff7b72= Te6edf3msgIdTb4b4b4)Tb4b4b4)Tb4b4b4.Te6edf3encodeTb4b4b4(Tb4b4b4)Tb4b4b4,
Tb4b4b4)

Tff7b72private Tff7b72fun Td2a8ffsendAdminTb4b4b4(Te6edf3fromInTb4b4b4: Tffa657IntTb4b4b4, Te6edf3toInTb4b4b4: Tffa657IntTb4b4b4, Te6edf3reqIdTb4b4b4: Tffa657IntTb4b4b4, Te6edf3initFnTb4b4b4: Te6edf3AdminMessageTb4b4b4.Tb4b4b4(Tb4b4b4) Tff7b72-Tff7b72> Te6edf3AdminMessageTb4b4b4) Tb4b4b4{
T8b949e// Embed a deterministic 8-byte fake passkey so SessionManager can record a session refresh β€” mirrors what real
T8b949e// firmware always attaches to admin responses (see firmware/src/modules/AdminModule.cpp:1460-1481).
Tff7b72val Te6edf3adminMsg Tff7b72= Te6edf3AdminMessageTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3initFnTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3copyTb4b4b4(Te6edf3session_passkey Tff7b72= Te6edf3FAKE_SESSION_PASSKEYTb4b4b4)
Tff7b72val Te6edf3p Tff7b72=
Te6edf3makeDataPacketTb4b4b4(
Te6edf3fromInTb4b4b4,
Te6edf3toInTb4b4b4,
Te6edf3DataTb4b4b4(Te6edf3portnum Tff7b72= Te6edf3PortNumTb4b4b4.Te6edf3ADMIN_APPTb4b4b4, Te6edf3payload Tff7b72= Te6edf3adminMsgTb4b4b4.Te6edf3encodeTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3toByteStringTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3request_id Tff7b72= Te6edf3reqIdTb4b4b4)Tb4b4b4,
Tb4b4b4)
Te6edf3callbackTb4b4b4.Te6edf3handleFromRadioTb4b4b4(Te6edf3pTb4b4b4.Te6edf3encodeTb4b4b4(Tb4b4b4)Tb4b4b4)
Tb4b4b4}

T8b949e// / Send a fake ack packet back if the sender asked for want_ack
Tff7b72private Tff7b72fun Td2a8ffsendFakeAckTb4b4b4(Te6edf3prTb4b4b4: Te6edf3ToRadioTb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3packet Tff7b72= Te6edf3prTb4b4b4.Te6edf3packet Tff7b72?: Tff7b72return
Te6edf3transportScopeTb4b4b4.Te6edf3handledLaunch Tb4b4b4{
Te6edf3delayTb4b4b4(Te6edf3ACK_DELAY_MSTb4b4b4)
Te6edf3lifecycleTb4b4b4.Te6edf3runIfOpen Tb4b4b4{
Te6edf3callbackTb4b4b4.Te6edf3handleFromRadioTb4b4b4(Te6edf3makeAckTb4b4b4(Te6edf3SIM_PEERSTff7b72[Te6edf3DIRECT_PEER_INDEXTff7b72]Tb4b4b4.Te6edf3numTb4b4b4, Te6edf3packetTb4b4b4.Te6edf3fromTb4b4b4, Te6edf3packetTb4b4b4.Te6edf3idTb4b4b4)Tb4b4b4.Te6edf3encodeTb4b4b4(Tb4b4b4)Tb4b4b4)
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}

T8b949e/** One simulated peer in the demo mesh. */
Tff7b72private Tff7b72data Tff7b72class T56d364SimPeerTb4b4b4(
Tff7b72val Te6edf3numTb4b4b4: Tffa657IntTb4b4b4,
Tff7b72val Te6edf3longNameTb4b4b4: Tffa657StringTb4b4b4,
Tff7b72val Te6edf3shortNameTb4b4b4: Tffa657StringTb4b4b4,
Tff7b72val Te6edf3hwModelTb4b4b4: Te6edf3HardwareModelTb4b4b4,
Tff7b72val Te6edf3roleTb4b4b4: Te6edf3ConfigTb4b4b4.Te6edf3DeviceConfigTb4b4b4.Te6edf3RoleTb4b4b4,
Tff7b72val Te6edf3latitudeTb4b4b4: Tffa657DoubleTb4b4b4,
Tff7b72val Te6edf3longitudeTb4b4b4: Tffa657DoubleTb4b4b4,
Tff7b72val Te6edf3altitudeTb4b4b4: Tffa657IntTb4b4b4,
Tff7b72val Te6edf3batteryLevelTb4b4b4: Tffa657IntTb4b4b4,
Tff7b72val Te6edf3voltageTb4b4b4: Tffa657FloatTb4b4b4,
Tff7b72val Te6edf3snrTb4b4b4: Tffa657FloatTb4b4b4,
Tff7b72val Te6edf3rssiTb4b4b4: Tffa657IntTb4b4b4,
Tff7b72val Te6edf3hopsTb4b4b4: Tffa657IntTb4b4b4,
Tff7b72val Te6edf3secondsSinceHeardTb4b4b4: Tffa657IntTb4b4b4,
Tff7b72val Te6edf3uptimeSecondsTb4b4b4: Tffa657IntTb4b4b4,
Tb4b4b4)

T8b949e/** Latitude/longitude/altitude triple, converted to the proto's scaled-integer representation on demand. */
Tff7b72private Tff7b72data Tff7b72class T56d364SimPositionTb4b4b4(Tff7b72val Te6edf3latitudeTb4b4b4: Tffa657DoubleTb4b4b4, Tff7b72val Te6edf3longitudeTb4b4b4: Tffa657DoubleTb4b4b4, Tff7b72val Te6edf3altitudeTb4b4b4: Tffa657IntTb4b4b4) Tb4b4b4{
Tff7b72fun Td2a8fftoProtoTb4b4b4(Tb4b4b4) Tff7b72= Te6edf3ProtoPositionTb4b4b4(
Te6edf3latitude_i Tff7b72= Te6edf3orgTb4b4b4.Te6edf3meshtasticTb4b4b4.Te6edf3coreTb4b4b4.Te6edf3modelTb4b4b4.Te6edf3PositionTb4b4b4.Te6edf3degITb4b4b4(Te6edf3latitudeTb4b4b4)Tb4b4b4,
Te6edf3longitude_i Tff7b72= Te6edf3orgTb4b4b4.Te6edf3meshtasticTb4b4b4.Te6edf3coreTb4b4b4.Te6edf3modelTb4b4b4.Te6edf3PositionTb4b4b4.Te6edf3degITb4b4b4(Te6edf3longitudeTb4b4b4)Tb4b4b4,
Te6edf3altitude Tff7b72= Te6edf3altitudeTb4b4b4,
Te6edf3time Tff7b72= Te6edf3nowSecondsTb4b4b4.Te6edf3toIntTb4b4b4(Tb4b4b4)Tb4b4b4,
T8b949e// 32 bits is "full precision"; the coarse end of the scale draws a large uncertainty circle instead of
T8b949e// placing the node where it actually is.
Te6edf3precision_bits Tff7b72= T79c0ff3T79c0ff2Tb4b4b4,
Te6edf3sats_in_view Tff7b72= T79c0ff9Tb4b4b4,
Te6edf3location_source Tff7b72= Te6edf3ProtoPositionTb4b4b4.Te6edf3LocSourceTb4b4b4.Te6edf3LOC_INTERNALTb4b4b4,
Tb4b4b4)
Tb4b4b4}

Tff7b72private Tff7b72companion Tff7b72object Tb4b4b4{
Tff7b72const Tff7b72val Te6edf3MY_NODE Tff7b72= T79c0ff0Te6edf3x42424242
Tff7b72const Tff7b72val Te6edf3BROADCAST_ADDR Tff7b72= Tff7b72-T79c0ff1 T8b949e// 0xffffffff
Tff7b72const Tff7b72val Te6edf3FIRMWARE_VERSION Tff7b72= Ta5d6ff"Ta5d6ff9.9.9.abcdefgTa5d6ff"
Tff7b72const Tff7b72val Te6edf3MY_NODE_STATUS Tff7b72= Ta5d6ff"Ta5d6ffRunning Demo Mode β€” no radio attached.Ta5d6ff"
Tff7b72const Tff7b72val Te6edf3PEER_NODE_STATUS Tff7b72= Ta5d6ff"Ta5d6ffSolar powered, up on the ridge.Ta5d6ff"
Tff7b72const Tff7b72val Te6edf3AUTO_REPLY_TEXT Tff7b72= Ta5d6ff"Ta5d6ffGot it, thanks! Message received on the demo mesh.Ta5d6ff"

T8b949e/** First packet id handed out; low enough to stay clear of ids the app generates for its own sends. */
Tff7b72const Tff7b72val Te6edf3FIRST_PACKET_ID Tff7b72= T79c0ff5T79c0ff0

T8b949e/**
* Floor of the simulated voltage drift, the counterpart to [MIN_BATTERY_PERCENT]: a nearly-flat Li-ion cell
* rests around here, so the charts settle on a plausible value instead of running off the bottom of the scale.
*/
Tff7b72const Tff7b72val Te6edf3MIN_CELL_VOLTAGE Tff7b72= T79c0ff3.2f
Tff7b72const Tff7b72val Te6edf3MIN_BATTERY_PERCENT Tff7b72= T79c0ff5
Tff7b72const Tff7b72val Te6edf3MAX_BATTERY_PERCENT Tff7b72= T79c0ff1T79c0ff0T79c0ff0
Tff7b72const Tff7b72val Te6edf3VOLTAGE_DRIFT_PER_TICK Tff7b72= T79c0ff0.01f

T8b949e/** Hop budget the simulated nodes transmit with; `hop_limit` is derived so the app can infer hop distance. */
Tff7b72const Tff7b72val Te6edf3DEFAULT_HOP_START Tff7b72= T79c0ff3
Tff7b72const Tff7b72val Te6edf3DIRECT_PEER_INDEX Tff7b72= T79c0ff0
Tff7b72const Tff7b72val Te6edf3TELEMETRY_PEER_COUNT Tff7b72= T79c0ff4
Tff7b72val Te6edf3WEATHER_PEER_INDEXES Tff7b72= Te6edf3listOfTb4b4b4(T79c0ff4Tb4b4b4)

T8b949e/** Spacing between seeded frames; the app timestamps rows on persist, so a burst would collapse together. */
Tff7b72const Tff7b72val Te6edf3SEED_SPACING_MS Tff7b72= T79c0ff1T79c0ff2T79c0ff0L
Tff7b72const Tff7b72val Te6edf3MESSAGE_SPACING_SECONDS Tff7b72= T79c0ff2T79c0ff4T79c0ff0
Tff7b72const Tff7b72val Te6edf3LIVE_TICK_MS Tff7b72= T79c0ff2T79c0ff0Te6edf3_000L
Tff7b72const Tff7b72val Te6edf3WEATHER_TICK_INTERVAL Tff7b72= T79c0ff3
Tff7b72const Tff7b72val Te6edf3REPLY_DELAY_MS Tff7b72= T79c0ff2Te6edf3_500L
Tff7b72const Tff7b72val Te6edf3ACK_DELAY_MS Tff7b72= T79c0ff2Te6edf3_000L

Tff7b72val Te6edf3FAKE_SESSION_PASSKEYTb4b4b4: Te6edf3okioTb4b4b4.Te6edf3ByteString Tff7b72= Te6edf3okioTb4b4b4.Te6edf3ByteStringTb4b4b4.Te6edf3ofTb4b4b4(T79c0ff0Te6edf3x00Tb4b4b4, T79c0ff0Te6edf3x11Tb4b4b4, T79c0ff0Te6edf3x22Tb4b4b4, T79c0ff0Te6edf3x33Tb4b4b4, T79c0ff0Te6edf3x44Tb4b4b4, T79c0ff0Te6edf3x55Tb4b4b4, T79c0ff0Te6edf3x66Tb4b4b4, T79c0ff0Te6edf3x77Tb4b4b4)

Tff7b72val Te6edf3MY_POSITION Tff7b72= Te6edf3SimPositionTb4b4b4(Te6edf3latitude Tff7b72= T79c0ff3T79c0ff2.776665Tb4b4b4, Te6edf3longitude Tff7b72= Tff7b72-T79c0ff9T79c0ff6.796989Tb4b4b4, Te6edf3altitude Tff7b72= T79c0ff1T79c0ff3T79c0ff8Tb4b4b4)

T8b949e/**
* The demo mesh. Deterministic on purpose β€” the same mesh every launch makes the demo reproducible for
* screenshots, support requests and store reviews. Spread over ~15 km so the map has something to fit.
*/
Tff7b72val Te6edf3SIM_PEERS Tff7b72=
Te6edf3listOfTb4b4b4(
Te6edf3SimPeerTb4b4b4(
Te6edf3num Tff7b72= Te6edf3MY_NODE Tff7b72+ T79c0ff1Tb4b4b4,
Te6edf3longName Tff7b72= Ta5d6ff"Ta5d6ffRiverside BaseTa5d6ff"Tb4b4b4,
Te6edf3shortName Tff7b72= Ta5d6ff"Ta5d6ffRVSDTa5d6ff"Tb4b4b4,
Te6edf3hwModel Tff7b72= Te6edf3HardwareModelTb4b4b4.Te6edf3HELTEC_V3Tb4b4b4,
Te6edf3role Tff7b72= Te6edf3ConfigTb4b4b4.Te6edf3DeviceConfigTb4b4b4.Te6edf3RoleTb4b4b4.Te6edf3CLIENTTb4b4b4,
Te6edf3latitude Tff7b72= T79c0ff3T79c0ff2.802Tb4b4b4,
Te6edf3longitude Tff7b72= Tff7b72-T79c0ff9T79c0ff6.769Tb4b4b4,
Te6edf3altitude Tff7b72= T79c0ff1T79c0ff5T79c0ff2Tb4b4b4,
Te6edf3batteryLevel Tff7b72= T79c0ff9T79c0ff2Tb4b4b4,
Te6edf3voltage Tff7b72= T79c0ff4.09fTb4b4b4,
Te6edf3snr Tff7b72= T79c0ff1T79c0ff1.5fTb4b4b4,
Te6edf3rssi Tff7b72= Tff7b72-T79c0ff6T79c0ff2Tb4b4b4,
Te6edf3hops Tff7b72= T79c0ff0Tb4b4b4,
Te6edf3secondsSinceHeard Tff7b72= T79c0ff4T79c0ff5Tb4b4b4,
Te6edf3uptimeSeconds Tff7b72= T79c0ff1T79c0ff2T79c0ff8Te6edf3_400Tb4b4b4,
Tb4b4b4)Tb4b4b4,
Te6edf3SimPeerTb4b4b4(
Te6edf3num Tff7b72= Te6edf3MY_NODE Tff7b72+ T79c0ff2Tb4b4b4,
Te6edf3longName Tff7b72= Ta5d6ff"Ta5d6ffTrail RunnerTa5d6ff"Tb4b4b4,
Te6edf3shortName Tff7b72= Ta5d6ff"Ta5d6ffTRLRTa5d6ff"Tb4b4b4,
Te6edf3hwModel Tff7b72= Te6edf3HardwareModelTb4b4b4.Te6edf3TRACKER_T1000_ETb4b4b4,
Te6edf3role Tff7b72= Te6edf3ConfigTb4b4b4.Te6edf3DeviceConfigTb4b4b4.Te6edf3RoleTb4b4b4.Te6edf3TRACKERTb4b4b4,
Te6edf3latitude Tff7b72= T79c0ff3T79c0ff2.7605Tb4b4b4,
Te6edf3longitude Tff7b72= Tff7b72-T79c0ff9T79c0ff6.8305Tb4b4b4,
Te6edf3altitude Tff7b72= T79c0ff1T79c0ff4T79c0ff5Tb4b4b4,
Te6edf3batteryLevel Tff7b72= T79c0ff6T79c0ff4Tb4b4b4,
Te6edf3voltage Tff7b72= T79c0ff3.87fTb4b4b4,
Te6edf3snr Tff7b72= T79c0ff6.25fTb4b4b4,
Te6edf3rssi Tff7b72= Tff7b72-T79c0ff8T79c0ff4Tb4b4b4,
Te6edf3hops Tff7b72= T79c0ff0Tb4b4b4,
Te6edf3secondsSinceHeard Tff7b72= T79c0ff1T79c0ff3T79c0ff0Tb4b4b4,
Te6edf3uptimeSeconds Tff7b72= T79c0ff4T79c0ff1Te6edf3_900Tb4b4b4,
Tb4b4b4)Tb4b4b4,
Te6edf3SimPeerTb4b4b4(
Te6edf3num Tff7b72= Te6edf3MY_NODE Tff7b72+ T79c0ff3Tb4b4b4,
Te6edf3longName Tff7b72= Ta5d6ff"Ta5d6ffOak Cliff RepeaterTa5d6ff"Tb4b4b4,
Te6edf3shortName Tff7b72= Ta5d6ff"Ta5d6ffOAKRTa5d6ff"Tb4b4b4,
Te6edf3hwModel Tff7b72= Te6edf3HardwareModelTb4b4b4.Te6edf3RAK4631Tb4b4b4,
Te6edf3role Tff7b72= Te6edf3ConfigTb4b4b4.Te6edf3DeviceConfigTb4b4b4.Te6edf3RoleTb4b4b4.Te6edf3ROUTERTb4b4b4,
Te6edf3latitude Tff7b72= T79c0ff3T79c0ff2.7395Tb4b4b4,
Te6edf3longitude Tff7b72= Tff7b72-T79c0ff9T79c0ff6.8215Tb4b4b4,
Te6edf3altitude Tff7b72= T79c0ff1T79c0ff8T79c0ff9Tb4b4b4,
Te6edf3batteryLevel Tff7b72= T79c0ff1T79c0ff0T79c0ff0Tb4b4b4,
Te6edf3voltage Tff7b72= T79c0ff4.14fTb4b4b4,
Te6edf3snr Tff7b72= T79c0ff9.0fTb4b4b4,
Te6edf3rssi Tff7b72= Tff7b72-T79c0ff7T79c0ff1Tb4b4b4,
Te6edf3hops Tff7b72= T79c0ff0Tb4b4b4,
Te6edf3secondsSinceHeard Tff7b72= T79c0ff2T79c0ff0Tb4b4b4,
Te6edf3uptimeSeconds Tff7b72= T79c0ff9T79c0ff0T79c0ff3Te6edf3_600Tb4b4b4,
Tb4b4b4)Tb4b4b4,
Te6edf3SimPeerTb4b4b4(
Te6edf3num Tff7b72= Te6edf3MY_NODE Tff7b72+ T79c0ff4Tb4b4b4,
Te6edf3longName Tff7b72= Ta5d6ff"Ta5d6ffDeep Ellum HandheldTa5d6ff"Tb4b4b4,
Te6edf3shortName Tff7b72= Ta5d6ff"Ta5d6ffDEEPTa5d6ff"Tb4b4b4,
Te6edf3hwModel Tff7b72= Te6edf3HardwareModelTb4b4b4.Te6edf3T_DECKTb4b4b4,
Te6edf3role Tff7b72= Te6edf3ConfigTb4b4b4.Te6edf3DeviceConfigTb4b4b4.Te6edf3RoleTb4b4b4.Te6edf3CLIENTTb4b4b4,
Te6edf3latitude Tff7b72= T79c0ff3T79c0ff2.7842Tb4b4b4,
Te6edf3longitude Tff7b72= Tff7b72-T79c0ff9T79c0ff6.7845Tb4b4b4,
Te6edf3altitude Tff7b72= T79c0ff1T79c0ff4T79c0ff1Tb4b4b4,
Te6edf3batteryLevel Tff7b72= T79c0ff4T79c0ff7Tb4b4b4,
Te6edf3voltage Tff7b72= T79c0ff3.74fTb4b4b4,
Te6edf3snr Tff7b72= Tff7b72-T79c0ff2.5fTb4b4b4,
Te6edf3rssi Tff7b72= Tff7b72-T79c0ff1T79c0ff0T79c0ff3Tb4b4b4,
Te6edf3hops Tff7b72= T79c0ff1Tb4b4b4,
Te6edf3secondsSinceHeard Tff7b72= T79c0ff3T79c0ff2T79c0ff0Tb4b4b4,
Te6edf3uptimeSeconds Tff7b72= T79c0ff9Te6edf3_800Tb4b4b4,
Tb4b4b4)Tb4b4b4,
Te6edf3SimPeerTb4b4b4(
Te6edf3num Tff7b72= Te6edf3MY_NODE Tff7b72+ T79c0ff5Tb4b4b4,
Te6edf3longName Tff7b72= Ta5d6ff"Ta5d6ffRooftop WeatherTa5d6ff"Tb4b4b4,
Te6edf3shortName Tff7b72= Ta5d6ff"Ta5d6ffWTHRTa5d6ff"Tb4b4b4,
Te6edf3hwModel Tff7b72= Te6edf3HardwareModelTb4b4b4.Te6edf3HELTEC_MESH_NODE_T114Tb4b4b4,
Te6edf3role Tff7b72= Te6edf3ConfigTb4b4b4.Te6edf3DeviceConfigTb4b4b4.Te6edf3RoleTb4b4b4.Te6edf3SENSORTb4b4b4,
Te6edf3latitude Tff7b72= T79c0ff3T79c0ff2.8145Tb4b4b4,
Te6edf3longitude Tff7b72= Tff7b72-T79c0ff9T79c0ff6.8055Tb4b4b4,
Te6edf3altitude Tff7b72= T79c0ff2T79c0ff0T79c0ff5Tb4b4b4,
Te6edf3batteryLevel Tff7b72= T79c0ff8T79c0ff8Tb4b4b4,
Te6edf3voltage Tff7b72= T79c0ff4.02fTb4b4b4,
Te6edf3snr Tff7b72= T79c0ff4.75fTb4b4b4,
Te6edf3rssi Tff7b72= Tff7b72-T79c0ff9T79c0ff1Tb4b4b4,
Te6edf3hops Tff7b72= T79c0ff1Tb4b4b4,
Te6edf3secondsSinceHeard Tff7b72= T79c0ff2T79c0ff1T79c0ff0Tb4b4b4,
Te6edf3uptimeSeconds Tff7b72= T79c0ff5T79c0ff1T79c0ff2Te6edf3_000Tb4b4b4,
Tb4b4b4)Tb4b4b4,
Te6edf3SimPeerTb4b4b4(
Te6edf3num Tff7b72= Te6edf3MY_NODE Tff7b72+ T79c0ff6Tb4b4b4,
Te6edf3longName Tff7b72= Ta5d6ff"Ta5d6ffLakeside SolarTa5d6ff"Tb4b4b4,
Te6edf3shortName Tff7b72= Ta5d6ff"Ta5d6ffLAKETa5d6ff"Tb4b4b4,
Te6edf3hwModel Tff7b72= Te6edf3HardwareModelTb4b4b4.Te6edf3STATION_G2Tb4b4b4,
Te6edf3role Tff7b72= Te6edf3ConfigTb4b4b4.Te6edf3DeviceConfigTb4b4b4.Te6edf3RoleTb4b4b4.Te6edf3CLIENTTb4b4b4,
Te6edf3latitude Tff7b72= T79c0ff3T79c0ff2.8365Tb4b4b4,
Te6edf3longitude Tff7b72= Tff7b72-T79c0ff9T79c0ff6.7325Tb4b4b4,
Te6edf3altitude Tff7b72= T79c0ff1T79c0ff6T79c0ff7Tb4b4b4,
Te6edf3batteryLevel Tff7b72= T79c0ff7T79c0ff3Tb4b4b4,
Te6edf3voltage Tff7b72= T79c0ff3.94fTb4b4b4,
Te6edf3snr Tff7b72= T79c0ff1.5fTb4b4b4,
Te6edf3rssi Tff7b72= Tff7b72-T79c0ff9T79c0ff8Tb4b4b4,
Te6edf3hops Tff7b72= T79c0ff2Tb4b4b4,
Te6edf3secondsSinceHeard Tff7b72= T79c0ff6T79c0ff4T79c0ff0Tb4b4b4,
Te6edf3uptimeSeconds Tff7b72= T79c0ff2T79c0ff5T79c0ff4Te6edf3_300Tb4b4b4,
Tb4b4b4)Tb4b4b4,
Te6edf3SimPeerTb4b4b4(
Te6edf3num Tff7b72= Te6edf3MY_NODE Tff7b72+ T79c0ff7Tb4b4b4,
Te6edf3longName Tff7b72= Ta5d6ff"Ta5d6ffBike CourierTa5d6ff"Tb4b4b4,
Te6edf3shortName Tff7b72= Ta5d6ff"Ta5d6ffBIKETa5d6ff"Tb4b4b4,
Te6edf3hwModel Tff7b72= Te6edf3HardwareModelTb4b4b4.Te6edf3TBEAMTb4b4b4,
Te6edf3role Tff7b72= Te6edf3ConfigTb4b4b4.Te6edf3DeviceConfigTb4b4b4.Te6edf3RoleTb4b4b4.Te6edf3TRACKERTb4b4b4,
Te6edf3latitude Tff7b72= T79c0ff3T79c0ff2.7688Tb4b4b4,
Te6edf3longitude Tff7b72= Tff7b72-T79c0ff9T79c0ff6.7492Tb4b4b4,
Te6edf3altitude Tff7b72= T79c0ff1T79c0ff3T79c0ff4Tb4b4b4,
Te6edf3batteryLevel Tff7b72= T79c0ff3T79c0ff1Tb4b4b4,
Te6edf3voltage Tff7b72= T79c0ff3.62fTb4b4b4,
Te6edf3snr Tff7b72= Tff7b72-T79c0ff6.5fTb4b4b4,
Te6edf3rssi Tff7b72= Tff7b72-T79c0ff1T79c0ff1T79c0ff2Tb4b4b4,
Te6edf3hops Tff7b72= T79c0ff2Tb4b4b4,
Te6edf3secondsSinceHeard Tff7b72= T79c0ff1Te6edf3_180Tb4b4b4,
Te6edf3uptimeSeconds Tff7b72= T79c0ff3Te6edf3_600Tb4b4b4,
Tb4b4b4)Tb4b4b4,
Te6edf3SimPeerTb4b4b4(
Te6edf3num Tff7b72= Te6edf3MY_NODE Tff7b72+ T79c0ff8Tb4b4b4,
Te6edf3longName Tff7b72= Ta5d6ff"Ta5d6ffField Kit EchoTa5d6ff"Tb4b4b4,
Te6edf3shortName Tff7b72= Ta5d6ff"Ta5d6ffECHOTa5d6ff"Tb4b4b4,
Te6edf3hwModel Tff7b72= Te6edf3HardwareModelTb4b4b4.Te6edf3T_ECHOTb4b4b4,
Te6edf3role Tff7b72= Te6edf3ConfigTb4b4b4.Te6edf3DeviceConfigTb4b4b4.Te6edf3RoleTb4b4b4.Te6edf3CLIENT_MUTETb4b4b4,
Te6edf3latitude Tff7b72= T79c0ff3T79c0ff2.7215Tb4b4b4,
Te6edf3longitude Tff7b72= Tff7b72-T79c0ff9T79c0ff6.7738Tb4b4b4,
Te6edf3altitude Tff7b72= T79c0ff1T79c0ff5T79c0ff8Tb4b4b4,
Te6edf3batteryLevel Tff7b72= T79c0ff5T79c0ff6Tb4b4b4,
Te6edf3voltage Tff7b72= T79c0ff3.81fTb4b4b4,
Te6edf3snr Tff7b72= T79c0ff3.25fTb4b4b4,
Te6edf3rssi Tff7b72= Tff7b72-T79c0ff9T79c0ff5Tb4b4b4,
Te6edf3hops Tff7b72= T79c0ff1Tb4b4b4,
Te6edf3secondsSinceHeard Tff7b72= T79c0ff2Te6edf3_400Tb4b4b4,
Te6edf3uptimeSeconds Tff7b72= T79c0ff7T79c0ff6Te6edf3_500Tb4b4b4,
Tb4b4b4)Tb4b4b4,
Tb4b4b4)

T8b949e/** Seeded channel conversation, as (peer index, text) pairs. Oldest first. */
Tff7b72val Te6edf3CHANNEL_CONVERSATION Tff7b72=
Te6edf3listOfTb4b4b4(
T79c0ff0 Te6edf3to Ta5d6ff"Ta5d6ffMorning all β€” base station is back online after the power cut.Ta5d6ff"Tb4b4b4,
T79c0ff2 Te6edf3to Ta5d6ff"Ta5d6ffCopy that. Repeater on Oak Cliff is holding steady, 100% battery.Ta5d6ff"Tb4b4b4,
T79c0ff1 Te6edf3to Ta5d6ff"Ta5d6ffOut on the trail loop, signal is solid the whole way today.Ta5d6ff"Tb4b4b4,
T79c0ff4 Te6edf3to Ta5d6ff"Ta5d6ffRooftop sensor reading 18.5C and 47% humidity if anyone cares.Ta5d6ff"Tb4b4b4,
T79c0ff0 Te6edf3to Ta5d6ff"Ta5d6ffNice. Net check complete, everyone reporting in.Ta5d6ff"Tb4b4b4,
Tb4b4b4)

T8b949e/** Seeded direct-message thread from [DIRECT_PEER_INDEX]. Oldest first. */
Tff7b72val Te6edf3DIRECT_CONVERSATION Tff7b72=
Te6edf3listOfTb4b4b4(
Ta5d6ff"Ta5d6ffHey, are you still planning to bring the spare antenna tomorrow?Ta5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffNo rush β€” just let me know before you set off.Ta5d6ff"Tb4b4b4,
Tb4b4b4)
Tb4b4b4}
Tb4b4b4}

Served by rngit 1.5.0 - Generated in 0.18s